package array;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Array {
    private final List<ArrayItem> items;
    public final int length;

    public Array(String str) {
        String src = str.trim();
        items = new ArrayList<>();
        length = check(src);
        parse(src);
    }

    /* 对传入的参数进行检查 */
    private static int check(String str) {
        // 检查两侧是否为[]
        if (str.charAt(0) != '[' || str.charAt(str.length() - 1) != ']') {
            throw new ParsingFormattedException("无效的array");
        }

        // 下面将逐个字符读取，围绕下述模式分逻辑进行处理
        // 分四种字符读取模式：1.字符串 2.token 3.子数组 4.normal
        // 1.字符串:   在normal模式下，从第一个双引号开始进入字符串读取模式，直到下一个遇到双引号时结束
        //            如果需要包含双引号的字符，则需要使用'\'对其进行转义
        // 2.token:   在normal模式下，从第一个非空字符开始进入token读取模式，直到下一个遇到','或']'时结束
        // 3.子数组:   在normal模式下，从第一个'['字符开始进入子数组读取模式，直到下一个与之匹配的']'时结束
        // 4.normal:  读取了首个字符后进入该模式，当从其他模式退出时回到该模式，直到下一次进入其他模式
        //            从其他模式退出回到该模式时，还会处于等待状态，当扫描到一个分隔符','变为就绪状态，之后才能进入其他模式

        // 进入字符串、token、子数组模式时，记录它的起始下标，当记录值为-1时表示还未进入对应模式
        int brokenStrOffset = -1;
        int brokenTokenOffset = -1;
        int subArrayOffset = -1;
        // 标志当前是否处于等待状态，需要寻找分隔符','
        boolean isNeedSep = false;
        // '['计数变量
        int leftBraceCnt = 0;
        // 解析到的元素个数
        int elementCnt = 0;

        // 空字符
        final String emptyChars = " \n\t";

        int length = str.length();
        for (int i = 0; i < length; i++) {
            char ch = str.charAt(i);
            boolean isEscape = i > 0 && str.charAt(i - 1) == '\\';

            if (brokenStrOffset != -1) {
                if (ch == '"' && !isEscape) {
                    brokenStrOffset = -1;
                    isNeedSep = true;
                    elementCnt++;
                }
                continue;
            }

            if (isEscape) {
                continue;
            }
            if (brokenTokenOffset != -1) {
                if (ch == '[' || ch == '"') {
                    throw new ParsingFormattedException(overInfo(str, i, ch));
                }
                if (ch == ',' || ch == ']') {
                    brokenTokenOffset = -1;
                    elementCnt++;
                    if (ch == ']') {
                        leftBraceCnt--;
                    }
                }
                continue;
            }

            if (isNeedSep) {
                if (ch == ',' || ch == ']') {
                    isNeedSep = false;
                    if (ch == ']') {
                        leftBraceCnt--;
                    }
                } else if (emptyChars.indexOf(ch) == -1) {
                    throw new ParsingFormattedException(info(str, i, "need a separator ',', find '" + ch + "'"));
                }

                if (leftBraceCnt == 0 && i < length - 1) {
                    throw new ParsingFormattedException(overInfo(str, i, ']'));
                }
                continue;
            }

            if (ch == '[') {
                if (leftBraceCnt == 1) {
                    subArrayOffset = i;
                }
                leftBraceCnt++;
            } else if (ch == ']') {
                leftBraceCnt--;
                if (leftBraceCnt < 0) {
                    throw new ParsingFormattedException(overInfo(str, i, ']'));
                }
                if (leftBraceCnt == 1) {
                    isNeedSep = true;
                    elementCnt++;
                }
            } else if (leftBraceCnt == 1) {
                if (ch == '"') {
                    brokenStrOffset = i;
                } else if (emptyChars.indexOf(ch) == -1) {
                    brokenTokenOffset = i;
                }
            }

            if (leftBraceCnt == 0 && i < length - 1) {
                throw new ParsingFormattedException(info(str, i, "find more character after root array, you can not input more than one array to be parsed"));
            }
        }
        if (brokenStrOffset != -1) {
            throw new ParsingFormattedException("broken str: " + str.substring(brokenStrOffset));
        }
        if (leftBraceCnt > 0) {
            throw new ParsingFormattedException(overInfo(str, subArrayOffset, '['));
        }
        return elementCnt;
    }

    private static String overInfo(String s, int pos, char c) {
        return info(s, pos, "多余的'" + c + "', 位置: " + pos);
    }

    private static String info(String s, int pos, String msg) {
        StringBuilder sb = new StringBuilder();
        sb.append(System.lineSeparator()).append(s).append(System.lineSeparator());
        for (int k = 0; k < pos; ++k) {
            sb.append(' ');
        }
        sb.append('^')
                .append(System.lineSeparator())
                .append(msg);
        return sb.toString();
    }

    private void parse(String str) {
        int idx = 0;
        int n = str.length();
        while (++idx < n) {
            // 跳过元素之间的空格
            while (idx < n && " \n\t".indexOf(str.charAt(idx)) != -1) ++idx;
            int begin = idx;
            if (str.charAt(idx) == '[') {
                int cnt = 1;
                while (++idx < n && cnt > 0) {
                    char c = str.charAt(idx);
                    if (c == ']') --cnt;
                    else if (c == '[') ++cnt;
                }
            } else {
                // 匹配元素，元素之间由 ',' 隔开
                // 忽略最后的 ']'
                if (str.charAt(idx) == '"') {
                    idx++;
                    while (idx < n - 1 && (str.charAt(idx) != '"' || str.charAt(idx - 1) == '\\')) idx++;
                    while (idx < n - 1 && str.charAt(idx) != ',') ++idx;
                } else {
                    while (idx < n - 1 && (str.charAt(idx) != ',' || str.charAt(idx - 1) == '\\')) ++idx;
                }
            }
            if (begin < idx) {
                // 确保存在元素
                items.add(new ArrayItem(str.substring(begin, idx).replaceAll("\\\\", "")));
            }
        }
        if (items.size() != length) {
            throw new RuntimeException("inner error!!! : item count doesn't match checked result");
        }
    }

    public ArrayItem get(int index) {
        return items.get(index);
    }

    /**
     * @return 以 {@code List} 形式返回当前 {@code Array} 对象存储的一系列元素. 元素的值是它一开始被解析时的文本
     */
    public List<String> convertToStringList() {
        return items.stream()
                .map(ArrayItem::getSrc)
                .collect(Collectors.toList());
    }

    @Override
    public String toString() {
        return "Array {\n" +
                "\titems=" + items +
                ",\n\tlength=" + length +
                "\n}";
    }
}
